/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

//=========================================================
// VWM miscellaneous
//=========================================================
VWM::VWM()
{
}

VWM::~VWM()
{
}

void VWM::initDesktops()
{
	// Start with one desktop for each monitor
	for(int ii=0; ii<getMinDesktops(); ii++)
	{
		VirtualDesktop *newDesk = createDesktop(ii);
		
		Monitor *monitor = monitors->getMonitor(ii);
		if(monitor) {
			newDesk->monitor = monitor;
			monitor->setDesk(newDesk);
		}
	}
	
	lastFocusedDesktop = NULL;
}

VirtualDesktop *VWM::getDeskFromWnd(HWND handle)
{
	if(!handle)
		return NULL;
	
	WindowData *window = windowTracker->getWindow(handle);
	if(window)
		return window->desk;
	
	RECT screenPos;
	GetWindowRect(handle, &screenPos);
	VirtualDesktop *desk = deskFromLocation(screenPos);
	if(desk)
		return desk;
	else
		return monitors->getPrimaryMonitor()->getDesk();
}

int VWM::getDeskIndex(VirtualDesktop *desk)
{
	for(int ii=0; ii<(int)desktops.size(); ii++)
	{
		if(desktops[ii] == desk)
			return ii;
	}
	return -1;
}

RECT VWM::getGatherTarget(RECT source, DeskSlot *target)
{
	Rect ret = source;
	Rect maximizeArea = target->getMaximizeArea();
	
	// If this is inside a VWM's storage area, first bring it in
	VirtualDesktop *desk = deskFromLocation(source);
	if(desk)
		desk->currentSlot()->unstoreRect(ret);
	target->storeRect(ret);
	
	int offsetX=0;
	int offsetY=0;
	
	// If a window is wider or taller than the screen, center it. Otherwise,
	// match an edge. (This happens more often than you'd think, because
	// maximized windows are a few pixels bigger than the maximize-area so
	// that their borders fall offscreen.)
	if(ret.width > maximizeArea.width) {
		if(ret.left > maximizeArea.left || ret.getRight() < maximizeArea.getRight())
			ret.left = maximizeArea.left - (ret.width-maximizeArea.width)/2;
	} else if(ret.getRight() > maximizeArea.getRight()) {
		ret.left -= ret.getRight() - maximizeArea.getRight();
	} else if(ret.left < maximizeArea.left) {
		ret.left = maximizeArea.left;
	}
	
	if(ret.height > maximizeArea.height) {
		if(ret.top > maximizeArea.top || ret.getBottom() < maximizeArea.getBottom())
			ret.top = maximizeArea.top - (ret.height-maximizeArea.height)/2;
	} else if(ret.getBottom() > maximizeArea.getBottom()) {
		ret.top += maximizeArea.getBottom() - ret.getBottom();
	} else if(ret.top < maximizeArea.top) {
		ret.top = maximizeArea.top;
	}
	
	return ret;
}

VirtualDesktop *VWM::deskFromLocation(Rect pos)
{
	// If it's on-screen, it's on the focused desk
	vector<Monitor*> monitorsShownOn = monitors->findMonitor(pos);
	if(monitorsShownOn.size())
		return monitorsShownOn[0]->getDesk();
	
	for(unsigned ii=0; ii<desktops.size(); ii++)
	{
		VirtualDesktop *desk = desktops[ii];
		Rect storageRect = desk->getOffScreenStorage()->getBoundingRect();
		if(storageRect.overlaps(pos)) {
			return desk;
		}
	}
	
	return NULL;
}

int VWM::numDesktops() const
{
	return desktops.size();
}

VirtualDesktop *VWM::getDesk(int index)
{
	if(index<0 || index>=(int)desktops.size())
		return NULL;
	else
		return desktops[index];
}

VirtualDesktop *VWM::getLastFocusedDesktop()
{
	return lastFocusedDesktop;
}

VirtualDesktop *VWM::createDesktop(int index)
{
	if(index < 0) index = 0;
	if(index > (int)desktops.size()) index = desktops.size();
	
	if(settings->maxDesktops > 0
	   && (int)desktops.size() >= settings->maxDesktops
	   && (int)desktops.size() >= getMinDesktops())
	{
		return NULL;
	}
	
	VirtualDesktop *newDesk = new VirtualDesktop(storageManager.getStorageArea());
	
	desktops.insert(desktops.begin()+index, newDesk);
	
	return newDesk;
}

bool VWM::destroyDesktop(VirtualDesktop *desk)
{
	// If there's only one desktop per monitor left, it can't be destroyed
	if((int)desktops.size() <= getMinDesktops())
		return false;
	
	// Pick a desktop to move windows to. Takes the numerically nearest
	// non-visible desk, ties going to the left.
	VirtualDesktop *mergeTarget = NULL;
	int bestDistance = -1;
	
	for(int ii=0; ii<(int)desktops.size(); ii++)
	{
		if(desk->isVisible() && desktops[ii]->isVisible())
			continue;
		if(desktops[ii] == desk)
			continue;
		int distance = abs(desktops[ii]->getIndex() - desk->getIndex());
		if(bestDistance==-1 || distance<bestDistance) {
			mergeTarget = desktops[ii];
			bestDistance = distance;
		}
	}
	
	if(!mergeTarget)
		return false;
	
	return mergeDesk(desk, mergeTarget);
}

bool VWM::mergeDesk(VirtualDesktop *deletedDesktop, VirtualDesktop *mergeTarget)
{
	// Can't merge a desk with itself
	if(deletedDesktop == mergeTarget)
		return false;
	
	// Can't merge if the result would be fewer desks than monitors
	if((int)desktops.size() <= monitors->numMonitors())
		return false;
	
	beginMovingWindows();
	
	// If deleting the current desktop, the merge target becomes the current desktop
	vector<WindowData*> windows;
	windowTracker->getWindows(deletedDesktop, windows);
	
	if(deletedDesktop->isVisible() && !mergeTarget->isVisible())
	{
		mergeTarget->show(deletedDesktop->getMonitor());
		mergeTarget->monitor = deletedDesktop->monitor;
		deletedDesktop->monitor->setDesk(mergeTarget);
	}
	else if(mergeTarget->isVisible() && !deletedDesktop->isVisible())
	{
		deletedDesktop->show(mergeTarget->getMonitor());
		mergeTarget->monitor->setDesk(mergeTarget);
	}
	else if(mergeTarget->isVisible() && deletedDesktop->isVisible())
	{
		// If both the deleted and target desks are visible, bail.
		// (This could be slightly more graceful.)
		return false;
	}
	else
	{
		for(unsigned ii=0; ii<windows.size(); ii++)
			deletedDesktop->moveWindowTo(windows[ii], mergeTarget);
	}
	
	for(unsigned ii=0; ii<windows.size(); ii++)
		windows[ii]->desk = mergeTarget;
	
	finishMovingWindows();
	
	if((int)desktops.size() > getMinDesktops())
	{
		// Remove the deleted desktop from the desk list and update everyone's indices
		desktops.erase(desktops.begin()+deletedDesktop->getIndex());
		
		delete deletedDesktop;
		
		// Clean up other possible references to the deleted desk
		if(lastFocusedDesktop == deletedDesktop)
			lastFocusedDesktop = NULL;
	}
	else
	{
		deletedDesktop->monitor = NULL;
	}
	
	forceTrackerUpdate();
	updateLayouts();
	return true;
}

void VWM::separateDesk(VirtualDesktop *desk)
{
	// If no desk specified, separate *every* desk
	if(!desk)
	{
		vector<VirtualDesktop*> originalDesks;
		originalDesks.insert(originalDesks.begin(), desktops.begin(), desktops.end());
		
		for(unsigned ii=0; ii<originalDesks.size(); ii++)
			separateDesk(originalDesks[ii]);
		return;
	}
	
	// Create new desktops for all but one of the tasks in this desk. The last
	// task stays on the original desktop.
	vector<VirtualDesktop*> newDesks;
	
	// Get the task list
	map<WindowData*, VirtualDesktop*> destinations;
	vector<TaskData*> tasks;
	windowTracker->getTasks(desk, tasks);
	
	// Decide which of the tasks uses the pre-existing desktop, and gets
	// switched to (if the split desk was visible)
	TaskData *foregroundTask = windowTracker->getForegroundTask();
	int foregroundTaskIndex = 0;
	for(unsigned ii=0; ii<tasks.size(); ii++)
	{
		if(tasks[ii] == foregroundTask) {
			foregroundTaskIndex = ii;
			break;
		}
	}
	
	// Create new desktops and decide which windows are going to which desktop
	for(unsigned ii=0; ii<tasks.size(); ii++)
	{
		VirtualDesktop *destination;
		if(ii==foregroundTaskIndex) {
			destination = desk;
		} else {
			destination = new VirtualDesktop(storageManager.getStorageArea());
		}
		newDesks.push_back(destination);
		
		vector<WindowData*> windows;
		tasks[ii]->getWindows(windows);
		for(unsigned jj=0; jj<windows.size(); jj++)
			destinations[windows[jj]] = destination;
	}
	
	// Insert desktops and set their indices
	desktops.insert(desktops.begin()+desk->getIndex()+foregroundTaskIndex+1,
	                newDesks.begin()+foregroundTaskIndex+1,
	                newDesks.end());
	desktops.insert(desktops.begin()+desk->getIndex(),
	                newDesks.begin(),
	                newDesks.begin()+foregroundTaskIndex);
	
	// Move windows to their new destinations
	beginMovingWindows();
	for(map<WindowData*, VirtualDesktop*>::iterator ii=destinations.begin(); ii!=destinations.end(); ii++)
		moveWindow(ii->first, ii->second);
	finishMovingWindows();
	
	forceTrackerUpdate();
	updateLayouts();
}

void VWM::moveDesk(int oldIndex, int newIndex)
{
	if(oldIndex<0 || newIndex<0 || oldIndex>=(int)desktops.size() || newIndex>=(int)desktops.size())
		return;
	
	VirtualDesktop *movingDesk = desktops[oldIndex];
	desktops.erase(desktops.begin()+oldIndex);
	desktops.insert(desktops.begin()+newIndex, movingDesk);
	
	int start = min(oldIndex, newIndex);
	int end = max(oldIndex, newIndex);
	
	updateLayouts();
}

void VWM::switchDesk(VirtualDesktop *newDesk, Monitor *switchingMonitor, bool skipFocus)
{
	if(!newDesk)
		return;
	if(!switchingMonitor)
		switchingMonitor = monitors->findMonitor(getCursorPos());
	VirtualDesktop *oldDesk = switchingMonitor->getDesk();
	if(newDesk == oldDesk)
		return;
	
	Monitor *swapMonitor = NULL;
	if(newDesk->monitor)
		swapMonitor = newDesk->monitor;
	
	beginMovingWindows();
	
	if(swapMonitor)
	{
		oldDesk->switchWith(newDesk);
	}
	else
	{
		oldDesk->hide();
		newDesk->show(switchingMonitor);
	}
	
	finishMovingWindows();
	
	lastFocusedDesktop = oldDesk;
	
	// Pick the topmost task on this desktop and focus it
	if(!skipFocus)
		windowTracker->raiseLocalForeground();
	
	forceTrackerUpdate();
	updateLayouts();
}

void VWM::raiseWindow(WindowData *window)
{
	SwitchToThisWindow(window->handle, TRUE);
	windowTracker->lastFocus = NULL;
	
	forceTrackerUpdate();
	updateLayouts();
}

void VWM::gather()
{
	Monitor *gatherTarget = monitors->getPrimaryMonitor();
	
	beginMovingWindows();
	
	// Move everything onto the current desktop
	vector<WindowData*> windows;
	windowTracker->getWindows(NULL, windows);
	for(unsigned ii=0; ii<windows.size(); ii++)
	{
		WindowData *window= windows[ii];
		
		transferWindow(window, window->desk->currentSlot(), gatherTarget);
		window->desk = gatherTarget->getDesk();
	}
	
	finishMovingWindows();
	
	// Delete all other desktops
	for(unsigned ii=0; ii<desktops.size(); )
	{
		if(!desktops[ii]->isVisible()) {
			delete desktops[ii];
			desktops.erase(desktops.begin()+ii);
		} else {
			ii++;
		}
	}
	
	forceTrackerUpdate();
	updateLayouts();
}

LayoutReference *findLayoutNodeMatching(Monitor *preferredMonitor, bool (*condition)(LayoutCacheNode*, void*), void *param)
{
	for(unsigned ii=0; ii<panels.size(); ii++)
	{
		ScreenbarPanel *panel = panels[ii];
		if(panel->getMonitor() == preferredMonitor)
		{
			vector<LayoutCacheNode*> layoutNodes;
			Layout *layout = panel->getLayout();
			if(layout)
			{
				LayoutReference *ref = layout->findNodeMatching(condition, param);
				if(ref) return ref;
			}
		}
	}

	for(unsigned ii=0; ii<panels.size(); ii++)
	{
		ScreenbarPanel *panel = panels[ii];
		if(panel->getMonitor() != preferredMonitor) {
			vector<LayoutCacheNode*> layoutNodes;
			Layout *layout = panel->getLayout();
			if(layout)
			{
				LayoutReference *ref = layout->findNodeMatching(condition, param);
				if(ref) return ref;
			}
		}
	}
	
	return NULL;
}

LayoutReference *getNodeAtPoint(Point pos)
{
	ScreenbarPanel *panel = getPanelAtPoint(pos);
	if(!panel) return NULL;
	
	Point pPos = pos - panel->getTopLeft();
	return panel->getLayout()->elementAtPoint(pPos.x, pPos.y);
}

ScreenbarPanel *getPanelAtPoint(Point pos)
{
	for(unsigned ii=0; ii<panels.size(); ii++)
	{
		ScreenbarPanel *panel = panels[ii];
		if(panel->getRect().containsPoint(pos.x, pos.y))
			return panel;
	}
	
	return NULL;
}

void VWM::deleteEmptyDesktops()
{
	if(settings->keepEmptyDesktops)
		return;
	
	for(unsigned ii=0; ii<desktops.size(); ii++)
	{
		if((int)desktops.size() <= getMinDesktops())
			break;
		
		VirtualDesktop *desk = desktops[ii];
		
		if(!windowTracker->deskIsEmpty(desk))
			continue;
		if(desk->isVisible() && settings->keepEmptyFocusedDesktop)
			continue;
		
		if(destroyDesktop(desk))
			ii--;
	}
}

int VWM::getMinDesktops()
{
	return max(settings->minDesktops, monitors->numMonitors());
}
